package com.koro.lock;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;

@Slf4j
@Component
public class RedisDistributedLockHandler {

    @Autowired
    private StringRedisTemplate template;

    /**
     * 尝试获取全局锁
     *
     * @param lock 锁的名称
     * @return true 获取成功，false获取失败
     */
    public boolean tryLock(Lock lock) {
        try {
            if (StringUtils.isEmpty(lock.getName()) || StringUtils.isEmpty(lock.getValue())) {
                return false;
            }
            long startTime = System.currentTimeMillis();
            do {
                if (getLock(lock)) {
                    return true;
                } else {
                    // 存在锁
                    log.debug("lock is existed");
                }
                if (System.currentTimeMillis() - startTime > lock.getTryTimeout()) {
                    // 尝试超过了设定值之后直接返回失败
                    return false;
                }
                Thread.sleep(lock.getTryInterval());
            } while (true);
        } catch (InterruptedException e) {
            log.error(e.getMessage());
            return false;
        }
    }

    public boolean getLock(Lock lock) {
        return template.opsForValue()
                .setIfAbsent(lock.getName(), lock.getValue(), lock.getExpire(), TimeUnit.MILLISECONDS);

    }

    /**
     * 释放锁
     */
    public void releaseLock(Lock lock) {
        if (!StringUtils.isEmpty(lock.getName())) {
            template.delete(lock.getName());
        }
    }

    public <T, R> R execute(Lock lock, Function<T, R> function, T param) {
        if (tryLock(lock)) {
            try {
                return function.apply(param);
            } catch (Exception e) {
                throw e;
            } finally {
                releaseLock(lock);
            }
        }
        return null;
    }

    public <R> R execute(Lock lock, Supplier<R> supplier) {
        if (tryLock(lock)) {
            try {
                return supplier.get();
            } catch (Exception e) {
                throw e;
            } finally {
                releaseLock(lock);
            }
        }
        return null;
    }

}
